import mime from 'mime'
import {
  FileInfo,
  UploadCustomRequestOptions,
  UploadOptions,
  CustomRequestType
} from './type'
import { transformSize, fileTypeCorrect } from '@/utils/file/file'
import axios from '@/utils/request'
import * as svgList from './icon'
import { useHttp } from '@/store/modules/http'

const useUpload = (props, httpProps, uploadRef, emits) => {
  // 所有文件列表
  const fileList = ref<Array<FileInfo>>([])
  // 携带每个文件的其他信息
  const fileDataList = ref<
    Array<{
      size: number
      id: string
      thumbnailUrl: string
      response?: Record<string, unknown>
      File?: File
      url?: string
    }>
  >([])
  // 被删除的文件列表id
  const deletedFileIdList = ref<Array<string>>([])

  // 文件上传前置锁：防止多选上传时导致超出最大上传数量
  let beforeUploadLock: null | Promise<unknown> = null

  /**  1. 上传前置方法，判断文件类型与大小与数量是否满足要求、同时存储file文件  **/
  const handleBeforeUpload = async (options: UploadOptions) => {
    try {
      if (beforeUploadLock) {
        await beforeUploadLock
      }
      beforeUploadLock = createBeforeUploadLock(options)
      return await beforeUploadLock
    } catch (err) {
      console.log(err)
    } finally {
      beforeUploadLock = null
    }
  }

  const createBeforeUploadLock = async (
    options: UploadOptions
  ): Promise<unknown> => {
    return new Promise(async (resolve) => {
      try {
        if (props.max === fileDataList.value.length) {
          window.$message.warning('文件数量超出！')
          return resolve(false)
        }
        const { file } = options.file
        if (!file) return resolve(false)
        const { accept, maxFileSize } = props
        // 判断是否有type值，如果没有则默认赋后缀名
        if (options.file.type === '') {
          const string = options.file.name.split('.')
          options.file.type = string[string.length - 1]
        }
        if (
          !fileTypeCorrect(options.file.type as string, accept.split(',')) &&
          !(props.onFileTypeCorrect && props.onFileTypeCorrect(file))
        ) {
          window.$message.warning('文件类型错误，请传入正确的文件类型！')
          return resolve(false)
        } else if (maxFileSize && file.size > maxFileSize) {
          window.$message.warning(
            `文件大小超出${transformSize(
              maxFileSize
            )}，请上传规定大小内的文件！`
          )
          return resolve(false)
        }

        // 暴露上传前状态给外层组件
        const parentStatus = props.onBeforeUpload
          ? await props.onBeforeUpload(options)
          : true

        // 同时存储file文件
        if (parentStatus) {
          fileDataList.value.push({
            id: options.file.id,
            thumbnailUrl: await getThumbnailUrl(file),
            size: file.size,
            File: file,
            url: URL.createObjectURL(file)
          })
        }
        resolve(parentStatus)
      } catch (err) {
        console.log(err)
      }
    })
  }

  /**  2. 获取内置缩略图  **/
  const getSvgUrl = (svg: string): string => {
    return 'data:image/svg+xml;base64,' + window.btoa(svg)
  }

  const thumbnailUrls = {
    pdf: getSvgUrl(svgList.pdfSvg),
    word: getSvgUrl(svgList.wordSvg),
    excel: getSvgUrl(svgList.excelSvg),
    file: getSvgUrl(svgList.fileSvg)
  }

  const getThumbnailUrl = (file: File): Promise<string> => {
    if (props.createThumbnailUrl) {
      return props.createThumbnailUrl(file)
    }
    return new Promise((resolve) => {
      const { type } = file
      const extension = mime.getExtension(type)
      if (!extension) return resolve(thumbnailUrls.file)

      if (type.includes('image')) {
        resolve(URL.createObjectURL(file))
      } else if (extension === 'pdf') {
        resolve(thumbnailUrls.pdf)
      } else if (['docx', 'doc'].includes(extension)) {
        resolve(thumbnailUrls.word)
      } else if (['xlsx', 'xls'].includes(extension)) {
        resolve(thumbnailUrls.excel)
      } else {
        resolve(thumbnailUrls.file)
      }
    })
  }

  // 3. 自定义上传功能
  const handleCustomRequest = async ({
    file,
    onFinish,
    onError,
    onProgress
  }: UploadCustomRequestOptions) => {
    if (deletedFileIdList.value.find((fileId) => fileId === file.id)) return
    try {
      let resData: unknown
      if (props.customRequest) {
        resData = await props.customRequest({
          file,
          http: props.http,
          onError,
          onProgress
        })
      } else {
        resData = await requestUpload({
          file,
          onError,
          onProgress
        })
      }
      if (resData) {
        addResponseData(file, resData)
        props.onFinish && props.onFinish(file, resData)
        onFinish()
      } else {
        onError()
      }
    } catch (err) {
      console.log(err)
      onError()
    }
  }

  const requestUpload = async ({
    file,
    onError,
    onProgress
  }: CustomRequestType) => {
    try {
      const formData = new FormData()
      const { action, method, name, headers, data } = httpProps.value
      formData.append(name as string, file.file as File)
      data &&
        Object.keys(data).forEach((key) => {
          formData.append(key as string, data[key])
        })

      const res = await axios({
        url: action,
        method: method,
        headers,
        data: formData,
        cancelName: file.id,
        onUploadProgress: (progressEvent) => {
          onProgress({
            percent: Math.ceil(
              (progressEvent.loaded / progressEvent.total) * 100
            )
          })
        }
      })
      const resData = getResponseData(res)
      return resData
    } catch (err) {
      onError()
    }
  }

  /**  4. 判断请求是否为异常状态、提取响应体  **/
  const getResponseData = (response): boolean | object => {
    return props.getResponse ? props.getResponse(response) : response || false
  }

  const addResponseData = (file: FileInfo, resData: unknown) => {
    const dataFile = fileDataList.value.find((data) => file.id === data.id)
    if (dataFile) {
      Object.assign(dataFile, {
        response: resData
      })
    }
  }

  /**  文件状态改变监听，获取fileList列表   **/
  const onChange = (options: UploadOptions): void => {
    fileList.value = options.fileList
      .filter((info) => {
        return !deletedFileIdList.value.includes(info.id)
      })
      .map((info) => {
        const dataFile = fileDataList.value.find((i) => i.id === info.id)
        return dataFile ? { ...info, ...dataFile } : info
      })
    emits('update', fileList.value)
  }

  /**  重新上传函数  **/
  const handleRetry = (fileInfo: FileInfo) => {
    if (unref(uploadRef)) unref(uploadRef)!.submit(fileInfo.id)
    emits('retry', fileInfo)
  }

  /**  取消上传函数  **/
  const handleCancel = (fileInfo: FileInfo) => {
    const http = useHttp()
    http.cancelToken(fileInfo.id)
    emits('cancel', fileInfo)
  }

  /**  删除函数  **/
  const handleDelete = (fileInfo: FileInfo) => {
    deletedFileIdList.value.push(fileInfo.id)
    fileList.value = fileList.value.filter((info) => {
      return !deletedFileIdList.value.includes(info.id)
    })
    fileDataList.value = fileDataList.value.filter((info) => {
      return !deletedFileIdList.value.includes(info.id)
    })
    emits('delete', fileInfo)
  }

  /**  下载函数  **/
  const handleDownload = (fileInfo: FileInfo) => {
    if (!fileInfo.url && !fileInfo.File) {
      return window.$message.warning('文件不存在！')
    }
    const link = document.createElement('a')
    link.download = fileInfo.name
    if (fileInfo.url) link.href = fileInfo.url
    else if (fileInfo.File) URL.createObjectURL(fileInfo.File)
    link.click()
    emits('download', fileInfo)
  }

  /**  清空上传列表  **/
  const handleClear = () => {
    fileList.value = []
    deletedFileIdList.value = []
    fileDataList.value = []
    uploadRef.value.clear()
  }

  return {
    fileList,
    onChange,
    handleBeforeUpload,
    handleCustomRequest,
    handleRetry,
    handleDelete,
    handleCancel,
    handleDownload,
    handleClear
  }
}

export default useUpload
